db3a7553b9e604d55207f510a08f4f89c5102b6b
[lhc/web/wiklou.git] / includes / specials / SpecialContributions.php
1 <?php
2 /**
3 * Special:Contributions, show user contributions in a paged list
4 * @file
5 * @ingroup SpecialPage
6 */
7
8 class SpecialContributions extends SpecialPage {
9
10 public function __construct() {
11 parent::__construct( 'Contributions' );
12 }
13
14 public function execute( $par ) {
15 global $wgUser, $wgOut, $wgLang, $wgRequest;
16
17 $this->setHeaders();
18 $this->outputHeader();
19
20 $this->opts = array();
21
22 if( $par == 'newbies' ) {
23 $target = 'newbies';
24 $this->opts['contribs'] = 'newbie';
25 } elseif( isset( $par ) ) {
26 $target = $par;
27 } else {
28 $target = $wgRequest->getVal( 'target' );
29 }
30
31 // check for radiobox
32 if( $wgRequest->getVal( 'contribs' ) == 'newbie' ) {
33 $target = 'newbies';
34 $this->opts['contribs'] = 'newbie';
35 }
36
37 $this->opts['deletedOnly'] = $wgRequest->getCheck( 'deletedOnly' );
38
39 if( !strlen( $target ) ) {
40 $wgOut->addHTML( $this->getForm() );
41 return;
42 }
43
44 $this->opts['limit'] = $wgRequest->getInt( 'limit', $wgUser->getOption('rclimit') );
45 $this->opts['target'] = $target;
46 $this->opts['topOnly'] = $wgRequest->getCheck( 'topOnly' );
47
48 $nt = Title::makeTitleSafe( NS_USER, $target );
49 if( !$nt ) {
50 $wgOut->addHTML( $this->getForm() );
51 return;
52 }
53 $id = User::idFromName( $nt->getText() );
54
55 if( $target != 'newbies' ) {
56 $target = $nt->getText();
57 $wgOut->setSubtitle( $this->contributionsSub( $nt, $id ) );
58 $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsgExt( 'contributions-title', array( 'parsemag' ),$target ) ) );
59 } else {
60 $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') );
61 $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'sp-contributions-newbies-title' ) ) );
62 }
63
64 if( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
65 $this->opts['namespace'] = intval( $ns );
66 } else {
67 $this->opts['namespace'] = '';
68 }
69
70 $this->opts['tagFilter'] = (string) $wgRequest->getVal( 'tagFilter' );
71
72 // Allows reverts to have the bot flag in recent changes. It is just here to
73 // be passed in the form at the top of the page
74 if( $wgUser->isAllowed( 'markbotedits' ) && $wgRequest->getBool( 'bot' ) ) {
75 $this->opts['bot'] = '1';
76 }
77
78 $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev';
79 # Offset overrides year/month selection
80 if( $skip ) {
81 $this->opts['year'] = '';
82 $this->opts['month'] = '';
83 } else {
84 $this->opts['year'] = $wgRequest->getIntOrNull( 'year' );
85 $this->opts['month'] = $wgRequest->getIntOrNull( 'month' );
86 }
87
88 // Add RSS/atom links
89 $this->setSyndicated();
90 $feedType = $wgRequest->getVal( 'feed' );
91 if( $feedType ) {
92 return $this->feed( $feedType );
93 }
94
95 if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) {
96
97 $wgOut->addHTML( $this->getForm() );
98
99 $pager = new ContribsPager( array(
100 'target' => $target,
101 'namespace' => $this->opts['namespace'],
102 'year' => $this->opts['year'],
103 'month' => $this->opts['month'],
104 'deletedOnly' => $this->opts['deletedOnly'],
105 'topOnly' => $this->opts['topOnly'],
106 ) );
107 if( !$pager->getNumRows() ) {
108 $wgOut->addWikiMsg( 'nocontribs', $target );
109 } else {
110 # Show a message about slave lag, if applicable
111 if( ( $lag = $pager->getDatabase()->getLag() ) > 0 )
112 $wgOut->showLagWarning( $lag );
113
114 $wgOut->addHTML(
115 '<p>' . $pager->getNavigationBar() . '</p>' .
116 $pager->getBody() .
117 '<p>' . $pager->getNavigationBar() . '</p>'
118 );
119 }
120
121
122 # Show the appropriate "footer" message - WHOIS tools, etc.
123 if( $target != 'newbies' ) {
124 $message = 'sp-contributions-footer';
125 if ( IP::isIPAddress( $target ) ) {
126 $message = 'sp-contributions-footer-anon';
127 } else {
128 $user = User::newFromName( $target );
129 if ( !$user || $user->isAnon() ) {
130 // No message for non-existing users
131 return;
132 }
133 }
134
135 $text = wfMsgNoTrans( $message, $target );
136 if( !wfEmptyMsg( $message, $text ) && $text != '-' ) {
137 $wgOut->wrapWikiMsg(
138 "<div class='mw-contributions-footer'>\n$1\n</div>",
139 array( $message, $target ) );
140 }
141 }
142 }
143 }
144
145 protected function setSyndicated() {
146 global $wgOut;
147 $wgOut->setSyndicated( true );
148 $wgOut->setFeedAppendQuery( wfArrayToCGI( $this->opts ) );
149 }
150
151 /**
152 * Generates the subheading with links
153 * @param $nt Title object for the target
154 * @param $id Integer: User ID for the target
155 * @return String: appropriately-escaped HTML to be output literally
156 * @todo Fixme: almost the same as getSubTitle in SpecialDeletedContributions.php. Could be combined.
157 */
158 protected function contributionsSub( $nt, $id ) {
159 global $wgSysopUserBans, $wgLang, $wgUser, $wgOut;
160
161 $sk = $wgUser->getSkin();
162
163 if ( $id === null ) {
164 $user = htmlspecialchars( $nt->getText() );
165 } else {
166 $user = $sk->link( $nt, htmlspecialchars( $nt->getText() ) );
167 }
168 $userObj = User::newFromName( $nt->getText(), /* check for username validity not needed */ false );
169 $talk = $nt->getTalkPage();
170 if( $talk ) {
171 # Talk page link
172 $tools[] = $sk->link( $talk, wfMsgHtml( 'sp-contributions-talk' ) );
173 if( ( $id !== null && $wgSysopUserBans ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) {
174 if( $wgUser->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
175 if ( $userObj->isBlocked() ) {
176 $tools[] = $sk->linkKnown( # Change block link
177 SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ),
178 wfMsgHtml( 'change-blocklink' )
179 );
180 $tools[] = $sk->linkKnown( # Unblock link
181 SpecialPage::getTitleFor( 'Ipblocklist' ),
182 wfMsgHtml( 'unblocklink' ),
183 array(),
184 array(
185 'action' => 'unblock',
186 'ip' => $nt->getDBkey()
187 )
188 );
189 }
190 else { # User is not blocked
191 $tools[] = $sk->linkKnown( # Block link
192 SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ),
193 wfMsgHtml( 'blocklink' )
194 );
195 }
196 }
197 # Block log link
198 $tools[] = $sk->linkKnown(
199 SpecialPage::getTitleFor( 'Log' ),
200 wfMsgHtml( 'sp-contributions-blocklog' ),
201 array(),
202 array(
203 'type' => 'block',
204 'page' => $nt->getPrefixedText()
205 )
206 );
207 }
208 # Other logs link
209 $tools[] = $sk->linkKnown(
210 SpecialPage::getTitleFor( 'Log' ),
211 wfMsgHtml( 'sp-contributions-logs' ),
212 array(),
213 array( 'user' => $nt->getText() )
214 );
215
216 # Add link to deleted user contributions for priviledged users
217 if( $wgUser->isAllowed( 'deletedhistory' ) ) {
218 $tools[] = $sk->linkKnown(
219 SpecialPage::getTitleFor( 'DeletedContributions', $nt->getDBkey() ),
220 wfMsgHtml( 'sp-contributions-deleted' )
221 );
222 }
223
224 # Add a link to change user rights for privileged users
225 $userrightsPage = new UserrightsPage();
226 if( $id !== null && $userrightsPage->userCanChangeRights( User::newFromId( $id ) ) ) {
227 $tools[] = $sk->linkKnown(
228 SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ),
229 wfMsgHtml( 'sp-contributions-userrights' )
230 );
231 }
232
233 wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) );
234
235 $links = $wgLang->pipeList( $tools );
236
237 // Show a note if the user is blocked and display the last block log entry.
238 if ( $userObj->isBlocked() ) {
239 LogEventsList::showLogExtract(
240 $wgOut,
241 'block',
242 $nt->getPrefixedText(),
243 '',
244 array(
245 'lim' => 1,
246 'showIfEmpty' => false,
247 'msgKey' => array(
248 $userObj->isAnon() ?
249 'sp-contributions-blocked-notice-anon' :
250 'sp-contributions-blocked-notice',
251 $nt->getText() # Support GENDER in 'sp-contributions-blocked-notice'
252 ),
253 'offset' => '' # don't use $wgRequest parameter offset
254 )
255 );
256 }
257 }
258
259 // Old message 'contribsub' had one parameter, but that doesn't work for
260 // languages that want to put the "for" bit right after $user but before
261 // $links. If 'contribsub' is around, use it for reverse compatibility,
262 // otherwise use 'contribsub2'.
263 if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) {
264 return wfMsgHtml( 'contribsub2', $user, $links );
265 } else {
266 return wfMsgHtml( 'contribsub', "$user ($links)" );
267 }
268 }
269
270 /**
271 * Generates the namespace selector form with hidden attributes.
272 * @return String: HTML fragment
273 */
274 protected function getForm() {
275 global $wgScript;
276
277 $this->opts['title'] = $this->getTitle()->getPrefixedText();
278 if( !isset( $this->opts['target'] ) ) {
279 $this->opts['target'] = '';
280 } else {
281 $this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] );
282 }
283
284 if( !isset( $this->opts['namespace'] ) ) {
285 $this->opts['namespace'] = '';
286 }
287
288 if( !isset( $this->opts['contribs'] ) ) {
289 $this->opts['contribs'] = 'user';
290 }
291
292 if( !isset( $this->opts['year'] ) ) {
293 $this->opts['year'] = '';
294 }
295
296 if( !isset( $this->opts['month'] ) ) {
297 $this->opts['month'] = '';
298 }
299
300 if( $this->opts['contribs'] == 'newbie' ) {
301 $this->opts['target'] = '';
302 }
303
304 if( !isset( $this->opts['tagFilter'] ) ) {
305 $this->opts['tagFilter'] = '';
306 }
307
308 if( !isset( $this->opts['topOnly'] ) ) {
309 $this->opts['topOnly'] = false;
310 }
311
312 $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
313
314 # Add hidden params for tracking except for parameters in $skipParameters
315 $skipParameters = array( 'namespace', 'deletedOnly', 'target', 'contribs', 'year', 'month', 'topOnly' );
316 foreach ( $this->opts as $name => $value ) {
317 if( in_array( $name, $skipParameters ) ) {
318 continue;
319 }
320 $f .= "\t" . Xml::hidden( $name, $value ) . "\n";
321 }
322
323 $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagFilter'] );
324
325 $f .= '<fieldset>' .
326 Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) .
327 Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parsemag' ) ),
328 'contribs', 'newbie' , 'newbie', $this->opts['contribs'] == 'newbie' ? true : false ) . '<br />' .
329 Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parsemag' ) ),
330 'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ? true : false ) . ' ' .
331 Html::input( 'target', $this->opts['target'], 'text', array(
332 'size' => '20',
333 'required' => ''
334 ) + ( $this->opts['target'] ? array() : array( 'autofocus' ) ) ) . ' '.
335 '<span style="white-space: nowrap">' .
336 Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
337 Xml::namespaceSelector( $this->opts['namespace'], '' ) .
338 '</span>' .
339 Xml::checkLabel( wfMsg( 'history-show-deleted' ),
340 'deletedOnly', 'mw-show-deleted-only', $this->opts['deletedOnly'] ) . '<br />' .
341 Xml::tags( 'p', null, Xml::checkLabel( wfMsg( 'sp-contributions-toponly' ),
342 'topOnly', 'mw-show-top-only', $this->opts['topOnly'] ) ) .
343 ( $tagFilter ? Xml::tags( 'p', null, implode( '&#160;', $tagFilter ) ) : '' ) .
344 Xml::openElement( 'p' ) .
345 '<span style="white-space: nowrap">' .
346 Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) .
347 '</span>' . ' ' .
348 Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) .
349 Xml::closeElement( 'p' );
350
351 $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' );
352 if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) )
353 $f .= "<p id='mw-sp-contributions-explain'>{$explain}</p>";
354
355 $f .= '</fieldset>' .
356 Xml::closeElement( 'form' );
357 return $f;
358 }
359
360 /**
361 * Output a subscription feed listing recent edits to this page.
362 * @param $type String
363 */
364 protected function feed( $type ) {
365 global $wgRequest, $wgFeed, $wgFeedClasses, $wgFeedLimit;
366
367 if( !$wgFeed ) {
368 global $wgOut;
369 $wgOut->addWikiMsg( 'feed-unavailable' );
370 return;
371 }
372
373 if( !isset( $wgFeedClasses[$type] ) ) {
374 global $wgOut;
375 $wgOut->addWikiMsg( 'feed-invalid' );
376 return;
377 }
378
379 $feed = new $wgFeedClasses[$type](
380 $this->feedTitle(),
381 wfMsgExt( 'tagline', 'parsemag' ),
382 $this->getTitle()->getFullUrl() . "/" . urlencode($this->opts['target'])
383 );
384
385 // Already valid title
386 $nt = Title::makeTitleSafe( NS_USER, $this->opts['target'] );
387 $target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText();
388
389 $pager = new ContribsPager( array(
390 'target' => $target,
391 'namespace' => $this->opts['namespace'],
392 'year' => $this->opts['year'],
393 'month' => $this->opts['month'],
394 'tagFilter' => $this->opts['tagFilter'],
395 'deletedOnly' => $this->opts['deletedOnly'],
396 'topOnly' => $this->opts['topOnly'],
397 ) );
398
399 $pager->mLimit = min( $this->opts['limit'], $wgFeedLimit );
400
401 $feed->outHeader();
402 if( $pager->getNumRows() > 0 ) {
403 while( $row = $pager->mResult->fetchObject() ) {
404 $feed->outItem( $this->feedItem( $row ) );
405 }
406 }
407 $feed->outFooter();
408 }
409
410 protected function feedTitle() {
411 global $wgContLanguageCode, $wgSitename;
412 $page = SpecialPage::getPage( 'Contributions' );
413 $desc = $page->getDescription();
414 return "$wgSitename - $desc [$wgContLanguageCode]";
415 }
416
417 protected function feedItem( $row ) {
418 $title = Title::MakeTitle( intval( $row->page_namespace ), $row->page_title );
419 if( $title ) {
420 $date = $row->rev_timestamp;
421 $comments = $title->getTalkPage()->getFullURL();
422 $revision = Revision::newFromTitle( $title, $row->rev_id );
423
424 return new FeedItem(
425 $title->getPrefixedText(),
426 $this->feedItemDesc( $revision ),
427 $title->getFullURL(),
428 $date,
429 $this->feedItemAuthor( $revision ),
430 $comments
431 );
432 } else {
433 return null;
434 }
435 }
436
437 protected function feedItemAuthor( $revision ) {
438 return $revision->getUserText();
439 }
440
441 protected function feedItemDesc( $revision ) {
442 if( $revision ) {
443 return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) .
444 htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
445 "</p>\n<hr />\n<div>" .
446 nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
447 }
448 return '';
449 }
450 }
451
452 /**
453 * Pager for Special:Contributions
454 * @ingroup SpecialPage Pager
455 */
456 class ContribsPager extends ReverseChronologicalPager {
457 public $mDefaultDirection = true;
458 var $messages, $target;
459 var $namespace = '', $mDb;
460
461 function __construct( $options ) {
462 parent::__construct();
463
464 $msgs = array( 'uctop', 'diff', 'newarticle', 'rollbacklink', 'diff', 'hist', 'rev-delundel', 'pipe-separator' );
465
466 foreach( $msgs as $msg ) {
467 $this->messages[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
468 }
469
470 $this->target = isset( $options['target'] ) ? $options['target'] : '';
471 $this->namespace = isset( $options['namespace'] ) ? $options['namespace'] : '';
472 $this->tagFilter = isset( $options['tagFilter'] ) ? $options['tagFilter'] : false;
473
474 $this->deletedOnly = !empty( $options['deletedOnly'] );
475 $this->topOnly = !empty( $options['topOnly'] );
476
477 $year = isset( $options['year'] ) ? $options['year'] : false;
478 $month = isset( $options['month'] ) ? $options['month'] : false;
479 $this->getDateCond( $year, $month );
480
481 $this->mDb = wfGetDB( DB_SLAVE, 'contributions' );
482 }
483
484 function getDefaultQuery() {
485 $query = parent::getDefaultQuery();
486 $query['target'] = $this->target;
487 return $query;
488 }
489
490 function getQueryInfo() {
491 global $wgUser;
492 list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond();
493
494 $conds = array_merge( $userCond, $this->getNamespaceCond() );
495 // Paranoia: avoid brute force searches (bug 17342)
496 if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
497 $conds[] = $this->mDb->bitAnd('rev_deleted',Revision::DELETED_USER) . ' = 0';
498 } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
499 $conds[] = $this->mDb->bitAnd('rev_deleted',Revision::SUPPRESSED_USER) .
500 ' != ' . Revision::SUPPRESSED_USER;
501 }
502 $join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' );
503
504 $queryInfo = array(
505 'tables' => $tables,
506 'fields' => array(
507 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'page_is_redirect',
508 'page_len','rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment',
509 'rev_minor_edit', 'rev_user', 'rev_user_text', 'rev_parent_id', 'rev_deleted'
510 ),
511 'conds' => $conds,
512 'options' => array( 'USE INDEX' => array('revision' => $index) ),
513 'join_conds' => $join_cond
514 );
515
516 ChangeTags::modifyDisplayQuery(
517 $queryInfo['tables'],
518 $queryInfo['fields'],
519 $queryInfo['conds'],
520 $queryInfo['join_conds'],
521 $queryInfo['options'],
522 $this->tagFilter
523 );
524
525 wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) );
526 return $queryInfo;
527 }
528
529 function getUserCond() {
530 $condition = array();
531 $join_conds = array();
532 if( $this->target == 'newbies' ) {
533 $tables = array( 'user_groups', 'page', 'revision' );
534 $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ );
535 $condition[] = 'rev_user >' . (int)($max - $max / 100);
536 $condition[] = 'ug_group IS NULL';
537 $index = 'user_timestamp';
538 # FIXME: other groups may have 'bot' rights
539 $join_conds['user_groups'] = array( 'LEFT JOIN', "ug_user = rev_user AND ug_group = 'bot'" );
540 } else {
541 $tables = array( 'page', 'revision' );
542 $condition['rev_user_text'] = $this->target;
543 $index = 'usertext_timestamp';
544 }
545 if( $this->deletedOnly ) {
546 $condition[] = "rev_deleted != '0'";
547 }
548 if( $this->topOnly ) {
549 $condition[] = "rev_id = page_latest";
550 }
551 return array( $tables, $index, $condition, $join_conds );
552 }
553
554 function getNamespaceCond() {
555 if( $this->namespace !== '' ) {
556 return array( 'page_namespace' => (int)$this->namespace );
557 } else {
558 return array();
559 }
560 }
561
562 function getIndexField() {
563 return 'rev_timestamp';
564 }
565
566 function getStartBody() {
567 return "<ul>\n";
568 }
569
570 function getEndBody() {
571 return "</ul>\n";
572 }
573
574 /**
575 * Generates each row in the contributions list.
576 *
577 * Contributions which are marked "top" are currently on top of the history.
578 * For these contributions, a [rollback] link is shown for users with roll-
579 * back privileges. The rollback link restores the most recent version that
580 * was not written by the target user.
581 *
582 * @todo This would probably look a lot nicer in a table.
583 */
584 function formatRow( $row ) {
585 global $wgUser, $wgLang, $wgContLang;
586 wfProfileIn( __METHOD__ );
587
588 $sk = $this->getSkin();
589 $rev = new Revision( $row );
590 $classes = array();
591
592 $page = Title::newFromRow( $row );
593 $page->resetArticleId( $row->rev_page ); // use process cache
594 $link = $sk->link(
595 $page,
596 htmlspecialchars( $page->getPrefixedText() ),
597 array(),
598 $page->isRedirect() ? array( 'redirect' => 'no' ) : array()
599 );
600 # Mark current revisions
601 $difftext = $topmarktext = '';
602 if( $row->rev_id == $row->page_latest ) {
603 $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
604 # Add rollback link
605 if( !$row->page_is_new && $page->quickUserCan( 'rollback' )
606 && $page->quickUserCan( 'edit' ) )
607 {
608 $topmarktext .= ' '.$sk->generateRollback( $rev );
609 }
610 }
611 # Is there a visible previous revision?
612 if( $rev->userCan( Revision::DELETED_TEXT ) && $rev->getParentId() !== 0 ) {
613 $difftext = $sk->linkKnown(
614 $page,
615 $this->messages['diff'],
616 array(),
617 array(
618 'diff' => 'prev',
619 'oldid' => $row->rev_id
620 )
621 );
622 } else {
623 $difftext = $this->messages['diff'];
624 }
625 $histlink = $sk->linkKnown(
626 $page,
627 $this->messages['hist'],
628 array(),
629 array( 'action' => 'history' )
630 );
631
632 $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true );
633 $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true );
634 if( $rev->userCan( Revision::DELETED_TEXT ) ) {
635 $d = $sk->linkKnown(
636 $page,
637 htmlspecialchars($date),
638 array(),
639 array( 'oldid' => intval( $row->rev_id ) )
640 );
641 } else {
642 $d = $date;
643 }
644 if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
645 $d = '<span class="history-deleted">' . $d . '</span>';
646 }
647
648 if( $this->target == 'newbies' ) {
649 $userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text );
650 $userlink .= ' ' . wfMsg( 'parentheses', $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) ) . ' ';
651 } else {
652 $userlink = '';
653 }
654
655 if( $rev->getParentId() === 0 ) {
656 $nflag = ChangesList::flag( 'newpage' );
657 } else {
658 $nflag = '';
659 }
660
661 if( $rev->isMinor() ) {
662 $mflag = ChangesList::flag( 'minor' );
663 } else {
664 $mflag = '';
665 }
666
667 // Don't show useless link to people who cannot hide revisions
668 $canHide = $wgUser->isAllowed( 'deleterevision' );
669 if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
670 if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
671 $del = $this->mSkin->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
672 } else {
673 $query = array(
674 'type' => 'revision',
675 'target' => $page->getPrefixedDbkey(),
676 'ids' => $rev->getId()
677 );
678 $del = $this->mSkin->revDeleteLink( $query,
679 $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
680 }
681 $del .= ' ';
682 } else {
683 $del = '';
684 }
685
686 $diffHistLinks = '(' . $difftext . $this->messages['pipe-separator'] . $histlink . ')';
687 $ret = "{$del}{$d} {$diffHistLinks} {$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}";
688
689 # Denote if username is redacted for this edit
690 if( $rev->isDeleted( Revision::DELETED_USER ) ) {
691 $ret .= " <strong>" . wfMsgHtml('rev-deleted-user-contribs') . "</strong>";
692 }
693
694 # Tags, if any.
695 list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' );
696 $classes = array_merge( $classes, $newClasses );
697 $ret .= " $tagSummary";
698
699 // Let extensions add data
700 wfRunHooks( 'ContributionsLineEnding', array( &$this, &$ret, $row ) );
701
702 $classes = implode( ' ', $classes );
703 $ret = "<li class=\"$classes\">$ret</li>\n";
704 wfProfileOut( __METHOD__ );
705 return $ret;
706 }
707
708 /**
709 * Get the Database object in use
710 *
711 * @return Database
712 */
713 public function getDatabase() {
714 return $this->mDb;
715 }
716
717 /**
718 * Overwrite Pager function and return a helpful comment
719 */
720 function getSqlComment() {
721 if ( $this->namespace || $this->deletedOnly ) {
722 return 'contributions page filtered for namespace or RevisionDeleted edits'; // potentially slow, see CR r58153
723 } else {
724 return 'contributions page unfiltered';
725 }
726 }
727
728 }